/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-通用能力封装层-公共组件
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.utility.context;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * 注意：
 * 使用到 SpringContextHolder 的 自定义工具类 尽量不要注入到 Spring容器 中，以避免项目在启动过程中引发空指针异常，导致启动失败。
 * 通过这种方式获取 Spring容器 中的对象时，Spring容器 是不会帮助我们判断两者之间的依赖关系的，这意味着两者注入Spring容器中的顺序
 * 充满了不确定性，当Spring容器中没有该 引用类型 的对象时，诸如 ApplicationContext.getBean(Class<?>) 等API 会直接返回 null。
 * <p>
 * 正确使用 SpringContextHolder 应该考虑的是：在使用它获取Spring容器中的对象时，请首先确保此时 Spring容器 已经完全启动成功。
 */
public class SpringContextHolder implements ApplicationContextAware, InitializingBean, DisposableBean {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 根据bean名称获取Spring容器中的对象
     */
    public static Object getBean(String beanName) {
        return applicationContext.getBean(beanName);
    }

    /**
     * 根据对象的声明类型获取Spring容器中的对象
     */
    public static <T> T getBean(Class<? extends T> clazz) {
        return applicationContext.getBean(clazz);
    }

    /**
     * 根据bean名称和对象的声明类型获取Spring容器中的对象
     */
    public static <T> T getBean(Class<? extends T> clazz, String beanName) {
        return applicationContext.getBean(beanName, clazz);
    }

    /**
     * 根据对象的声明类型获取Spring容器中的相同声明类型对象的集合
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return applicationContext.getBeansOfType(clazz);
    }

    /**
     * 根据描述对象的注解类型获取Spring容器中的对象
     */
    public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> clazz) {
        return applicationContext.getBeansWithAnnotation(clazz);
    }

    // region bean-lifecycle 忘记怎么用时过来抄吧

    /**
     * 在Spring容器销毁该bean的时候获得一次回调。
     * DisposableBean : this method can be replaced with destroy-method
     */
    @Override
    public void destroy() throws Exception {
    }

    /**
     * 在spring初始化bean的时候，会自动调用afterPropertiesSet方法。
     * InitializingBean : this method can be replaced with init-method
     */
    @Override
    public void afterPropertiesSet() throws Exception {
    }
    // endregion

    // region servlet-lifecycle 忘记怎么用时过来抄吧

    /**
     * 被@PostConstruct注解的方法在项目启动的时候执行这个方法， 也可以理解为在spring容器启动的时候执行， 可作为一些数据的常规化加载，比如数据字典之类的。
     */
    @PostConstruct
    public void postConstruct() {
    }

    /**
     * 被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行， 并且只会被服务器调用一次，类似于Servlet的destroy()方法。 被@PreDestroy修饰的方法会在destroy()方法之后运行，在Servlet被彻底卸载之前。
     * 若要想实现优雅关闭资源，使用@PreDestroy注解即可。
     */
    @PreDestroy
    public void preDestroy() {
    }
    // endregion

}
